/*-
 * Copyright 2003-2005 Colin Percival
 * All rights reserved
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted providing that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include "xzdec.h"
#include "nbpatch.h"

int err_code = 0;
int err_line = 0;
int err_info = 0;
static void *nbpatch_buffer;

void nbpatch_buffer_init()
{
    nbpatch_buffer = NULL;
}

void * nbpatch_buffer_alloc(size_t size)
{
    if(nbpatch_buffer != NULL) {
#if (!defined BOARD_ESP8266)
        free(nbpatch_buffer);
#endif
    }
#if (!defined BOARD_ESP8266)
    nbpatch_buffer = malloc(size);
#else
    nbpatch_buffer = (void *)IRAM_HEAP_BASE;
#endif
    return nbpatch_buffer;
}

void * nbpatch_buffer_get()
{
    return nbpatch_buffer;
}

void nbpatch_buffer_free()
{
    if(nbpatch_buffer != NULL) {
#if (!defined BOARD_ESP8266)
        free(nbpatch_buffer);
#endif
    }
    nbpatch_buffer = NULL;
}

off_t offtin(u_char *buf) {
    off_t y;

    y = buf[7] & 0x7F;
    y = y * 256;
    y += buf[6];
    y = y * 256;
    y += buf[5];
    y = y * 256;
    y += buf[4];
    y = y * 256;
    y += buf[3];
    y = y * 256;
    y += buf[2];
    y = y * 256;
    y += buf[1];
    y = y * 256;
    y += buf[0];

    if (buf[7] & 0x80)
        y = -y;

    return y;
}

static off_t nbpatch_section(const unsigned long src, off_t old_end, unsigned long dst, off_t *seek_pos, off_t splict_size, int diff_size) ;

off_t nbpatch(unsigned long old_t, off_t old_size, const unsigned long new_t, off_t new_size, off_t splict_size) {
    int ret = -1;
    PatchStatus * pstatus = NULL; 

    if (!old_t || !new_t || old_size <= 0 || new_size <= 0 || splict_size <= 0) {
        return NBDIFF_PARAMS_INPUT_ERROR;
    }

    off_t seekpos = 0;
    off_t patchsize = 0;
    off_t pendingsize = 0;
    int num = 0;
    pstatus = nbpatch_get_pstatus();
    if(pstatus == NULL) {
        return NBDIFF_GET_PSTATUS_FAIL;
    }

    memset(pstatus, 0, sizeof(PatchStatus));
    read_patch_status(pstatus);

    num = pstatus->num;
    seekpos = pstatus->seekpos;
    patchsize = pstatus->patched_size;
    pendingsize = pstatus->pending_size;

    pendingsize = nbpatch_section(old_t, old_size, new_t, &seekpos, splict_size, new_size);


    ret = pendingsize;
nbpatch_error:
    LOG("nbpatch relsult: %d\n",ret);
    pstatus->num = 0;
    pstatus->patched_size = 0;
    pstatus->seekpos = 0;
    pstatus->status = 0;
    pstatus->crc = 0;
    pstatus->pending_size = 0;
    save_patch_status(pstatus);
    return ret;
}

static off_t nbpatch_section(const unsigned long src, off_t old_size, unsigned long dst, off_t *seek_pos, off_t splict_size, int diff_size)
{
    off_t newsize = 0;
    u_char header[HEADER_SIZE], buf[8];
    off_t oldpos = 0;
    off_t newpos = 0;
    off_t ctrl[3] = {0};
    off_t lenread = 0;
    off_t i = 0;
    struct xz_dec *ctrl_dec = NULL;
    struct xz_buf cb;
    struct xz_dec *data_dec = NULL;
    struct xz_buf db;

    int success = 0;

    off_t copynum;
	off_t addnum;
	off_t datapos;
	u_char *writeBuf;//读取缓存区
	u_char *readBuf;//写入缓存区
	off_t bufSize;//缓存区大小
	int blockSize;
	int blockCount;

    ERR_CODE_CLEAR();
    LOG("nbpatch begin ...");
    LOG("diff_size:%d",diff_size);
    LOG("src:%ld", src);
    LOG("old_size:%ld", old_size);
    LOG("dst:%ld", dst);
    LOG("seek_pos:%ld", *seek_pos);
    LOG("splict_size:%ld", splict_size);

    if(!src || !dst || !seek_pos ) {
        ERR_CODE_SET(NBDIFF_PARAMS_INPUT_ERROR, src);
        goto patch_error;
    }
    off_t seekpos = 0;

    rec_wdt_feed();

    int ret = nbpatch_read(dst, header, seekpos, HEADER_SIZE, 0);
    if(ret < 0) {
        ERR_CODE_SET(NBDIFF_FILE_OP_FAIL, ret);
        goto patch_error;
    }

    /* Check for appropriate magic */
    if (memcmp(header, "BSDIFF40", 8) != 0) {
        ERR_CODE_SET(NBDIFF_LZAM_OP_FAIL, 0);
        goto patch_error;
    }



    newsize=offtin(header+8);
	blockSize=offtin(header+16);
	blockCount=offtin(header+24);
	datapos=offtin(header+32);

    LOG("newsize:%ld", newsize);
    LOG("blockSize:%ld", blockSize);
    LOG("blockCount:%ld", blockCount);
    LOG("datapos:%ld", datapos);




    writeBuf = malloc((blockSize+1)*sizeof(u_char));
	readBuf = malloc((blockSize+1)*sizeof(u_char));


    if ((newsize < 0) || (blockSize < 0) || (blockCount < 0) || (datapos < 0)) {
        ERR_CODE_SET(NBDIFF_LZAM_OP_FAIL, 0);
        goto patch_error;
    }


    success = xz_init(&ctrl_dec, &cb);
    if (!success) {
        ERR_CODE_SET(NBDIFF_LZAM_OP_FAIL, success);
        goto patch_error;
    }

    success = xz_init(&data_dec, &db);
    if (!success) {
        ERR_CODE_SET(NBDIFF_LZAM_OP_FAIL, success);
        goto patch_error;
    }

    xzReadHandler cbhandler;
    memset(&cbhandler, 0, sizeof(xzReadHandler));
    //cbhandler.avail_size = bzctrllen;
    cbhandler.avail_size = datapos-HEADER_SIZE;
    cbhandler.read_pos = HEADER_SIZE;

    xzReadHandler datahandler;
    memset(&datahandler, 0, sizeof(xzReadHandler));
    //datahandler.avail_size = bzdatalen;
    // datahandler.read_pos = seekpos + HEADER_SIZE + bzctrllen;
    datahandler.avail_size = diff_size - datapos;
    datahandler.read_pos = datapos;

    off_t seekpos_ctrl = HEADER_SIZE;
    off_t seekpos_data = datapos;

    //execute ctrl
	for(int bi=0;bi<blockCount;bi++){
		LOG("bi:%d",bi);
        lenread = xz_read(&cbhandler, &cb, ctrl_dec, dst, buf, 8);

        
            
		int blockId = offtin(buf);
        LOG("blockID:%d",blockId);
		off_t writeSum=0;
		int currBlockSize = blockSize;
		off_t startPos,endPos,currPos;
		startPos = blockId*blockSize;
		if(blockId==(blockCount-1)){
			currBlockSize = newsize-blockSize*blockId;		
		}
		while(writeSum<currBlockSize){
			for(i=0;i<=2;i++) {
                lenread = xz_read(&cbhandler, &cb, ctrl_dec, dst, buf, 8);

                if (lenread < 8) {
                    ERR_CODE_SET(NBDIFF_LZAM_OP_FAIL, lenread);
                    goto patch_error;
                }
				ctrl[i]=offtin(buf);
			};
			if(ifdebug) printf("[%ld,%ld,%ld]\n",ctrl[0],ctrl[1],ctrl[2]);

			off_t copyLen = ctrl[0];
			off_t addLen = ctrl[1];
			oldpos += ctrl[2];

            lenread = xz_read(&datahandler, &db, data_dec, dst, readBuf, copyLen);

            if ((lenread < copyLen)){
                ERR_CODE_SET(NBDIFF_LZAM_OP_FAIL, lenread);
                goto patch_error;
            }
            nbpatch_read(src, writeBuf+writeSum, oldpos, copyLen, 1);

			/* Add old data to diff string */
			for(i=0;i<copyLen;i++){
				 writeBuf[i+writeSum]=writeBuf[i+writeSum]+readBuf[i];
			}
			writeSum += copyLen;
			oldpos += copyLen;
			if(ifdebug) printf("Add[%ld,%ld]\n",bi*blockSize+writeSum,addLen);

            /* Read extra string */
            
            lenread = xz_read(&datahandler, &db, data_dec, dst, writeBuf + writeSum, addLen);

            if (lenread < addLen) {
                ERR_CODE_SET(NBDIFF_LZAM_OP_FAIL, lenread);
                goto patch_error;
            }
			writeSum += addLen;
            rec_wdt_feed();
		}


		if(writeSum == currBlockSize) {
            patch_flash_erase(HAL_PARTITION_APPLICATION, startPos, blockSize);
            ret = patch_flash_write(HAL_PARTITION_APPLICATION, writeBuf, startPos, writeSum);       
		}else{
			ERR_CODE_SET(NBDIFF_LZAM_OP_FAIL, writeSum);
            goto patch_error;
		}

	}

// #if (AOS_OTA_RECOVERY_TYPE == OTA_RECOVERY_TYPE_DIRECT)
//     ret = save_bakeup_data((unsigned long )newbuf, newsize);
//     if(ret < 0) {
//         ERR_CODE_SET(NBDIFF_FILE_OP_FAIL, ret);
//         goto patch_error;
//     }
// #endif

    // seekpos += HEADER_SIZE;
    // seekpos += bzctrllen;
    // seekpos += bzdatalen;
    // seekpos += bzextralen;
    // *seek_pos = seekpos;

patch_error:
    if(err_code) {
        ERR_CODE_LOG();
    } else {
        LOG("nbpatch num:%d succ ", 0);
    }

    if(ctrl_dec){
        xz_end(ctrl_dec);
        ctrl_dec = NULL;
    }
    return newsize;
}
